Skip to content

Conversation

@Sheraff
Copy link
Contributor

@Sheraff Sheraff commented Nov 18, 2025

It seems some theoretically invalid route tree configurations used to work before #5867 but don't work anymore.

This PR proposes that we add some runtime checks (when NODE_ENV !== 'production') to warn users of such patterns.

Those env checks persist in the dist build of tanstack/router-core distributed through npm

Screenshot 2025-11-19 at 09 49 11

But get erased at build-time (tested w/ Vite rollup 7.1.12) when the production environment is set (vite build --mode production or simply vite build)

Screenshot 2025-11-19 at 09 51 15

Summary by CodeRabbit

  • Bug Fixes
    • Added development-time validation that surfaces clear errors for invalid route configurations—prevents index routes from having children and ensures parent/child route relationships are consistent, helping catch routing setup mistakes earlier.

@nx-cloud
Copy link

nx-cloud bot commented Nov 18, 2025

🤖 Nx Cloud AI Fix Eligible

An automatically generated fix could have helped fix failing tasks for this run, but Self-healing CI is disabled for this workspace. Visit workspace settings to enable it and get automatic fixes in future runs.

To disable these notifications, a workspace admin can disable them in workspace settings.


View your CI Pipeline Execution ↗ for commit 60e5fea

Command Status Duration Result
nx affected --targets=test:eslint,test:unit,tes... ❌ Failed 16m 37s View ↗
nx run-many --target=build --exclude=examples/*... ✅ Succeeded 1m 24s View ↗

☁️ Nx Cloud last updated this comment at 2025-11-18 23:03:58 UTC

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 18, 2025

Walkthrough

Runtime validation is added (development-only) to BaseRoute construction and child attachment to ensure index routes (non-root routes whose fullPath ends with /) are not assigned child routes and parent-child relationships are consistent.

Changes

Cohort / File(s) Summary
Route validation guards
packages/router-core/src/route.ts
Adds two runtime invariant checks (guarded by NODE_ENV !== 'production'): one in BaseRoute constructor/init to validate parent-child integrity and one in child-attachment logic to prevent index routes (trailing / for non-root) from having children. No public API signatures changed.

Sequence Diagram(s)

sequenceDiagram
  participant Builder as RouterBuilder
  participant Base as BaseRoute (init / ctor)
  participant AddChildren as RouteAddChildren (attach children)
  participant Dev as Developer

  Note over Builder,Base: Route creation (dev-only validations applied)
  Builder->>Base: construct/init route with optional parent
  alt parent exists and is invalid index-parent or missing child relation
    Base-->>Dev: throw invariant error (parent-child integrity)
  else
    Base->>AddChildren: proceed to attach children (if any)
    alt route.fullPath ends with '/' and has children
      AddChildren-->>Dev: throw invariant error (index route cannot have children)
    else
      AddChildren->>Builder: children attached (success)
    end
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

  • Localized, small changes in a single file following a consistent pattern.
  • Low logic density: two development-only guard clauses.

Areas to review:

  • Correct detection of index routes (trailing / semantics for root vs non-root).
  • Clarity and correctness of invariant error messages.

Suggested reviewers

  • schiller-manuel

Poem

🐰 Hopping through paths with careful paws,

I check each parent, enforce the laws.
No index may sprout a child anew,
I thump and warn, "This won't do!"
Dev-time safety — hops for you ✨

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and specifically describes the main change: adding error validation when children routes are added to index routes during route tree building.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor-router-core-route-errors-w-index-children

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link

pkg-pr-new bot commented Nov 18, 2025

More templates

@tanstack/arktype-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/arktype-adapter@5897

@tanstack/directive-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/directive-functions-plugin@5897

@tanstack/eslint-plugin-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/eslint-plugin-router@5897

@tanstack/history

npm i https://pkg.pr.new/TanStack/router/@tanstack/history@5897

@tanstack/nitro-v2-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/nitro-v2-vite-plugin@5897

@tanstack/react-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router@5897

@tanstack/react-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-devtools@5897

@tanstack/react-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-router-ssr-query@5897

@tanstack/react-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start@5897

@tanstack/react-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-client@5897

@tanstack/react-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/react-start-server@5897

@tanstack/router-cli

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-cli@5897

@tanstack/router-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-core@5897

@tanstack/router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools@5897

@tanstack/router-devtools-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-devtools-core@5897

@tanstack/router-generator

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-generator@5897

@tanstack/router-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-plugin@5897

@tanstack/router-ssr-query-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-ssr-query-core@5897

@tanstack/router-utils

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-utils@5897

@tanstack/router-vite-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/router-vite-plugin@5897

@tanstack/server-functions-plugin

npm i https://pkg.pr.new/TanStack/router/@tanstack/server-functions-plugin@5897

@tanstack/solid-router

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router@5897

@tanstack/solid-router-devtools

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-devtools@5897

@tanstack/solid-router-ssr-query

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-router-ssr-query@5897

@tanstack/solid-start

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start@5897

@tanstack/solid-start-client

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-client@5897

@tanstack/solid-start-server

npm i https://pkg.pr.new/TanStack/router/@tanstack/solid-start-server@5897

@tanstack/start-client-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-client-core@5897

@tanstack/start-plugin-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-plugin-core@5897

@tanstack/start-server-core

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-server-core@5897

@tanstack/start-static-server-functions

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-static-server-functions@5897

@tanstack/start-storage-context

npm i https://pkg.pr.new/TanStack/router/@tanstack/start-storage-context@5897

@tanstack/valibot-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/valibot-adapter@5897

@tanstack/virtual-file-routes

npm i https://pkg.pr.new/TanStack/router/@tanstack/virtual-file-routes@5897

@tanstack/zod-adapter

npm i https://pkg.pr.new/TanStack/router/@tanstack/zod-adapter@5897

commit: 60e5fea

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ca33d7 and 72feec7.

📒 Files selected for processing (1)
  • packages/router-core/src/route.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/route.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/router-core/src/route.ts
📚 Learning: 2025-10-01T18:31:35.420Z
Learnt from: schiller-manuel
Repo: TanStack/router PR: 5330
File: e2e/react-start/custom-basepath/src/routeTree.gen.ts:58-61
Timestamp: 2025-10-01T18:31:35.420Z
Learning: Do not review files named `routeTree.gen.ts` in TanStack Router repositories, as these are autogenerated files that should not be manually modified.

Applied to files:

  • packages/router-core/src/route.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test
🔇 Additional comments (1)
packages/router-core/src/route.ts (1)

1744-1751: Confirm the implementation is correct and reliable.

The verification confirms that detecting index routes via fullPath.endsWith('/') is a reliable pattern throughout the codebase:

  1. Construction: Index routes consistently have fullPath ending with '/' because they use empty path values that get joined with parent paths via joinPaths([this.parentRoute.fullPath, path]).

  2. Consistency: The pattern is used identically at line 679 in new-process-route-tree.ts and verified through comprehensive tests across static, dynamic, and optional route types.

  3. Validation: Test suite (new-process-route-tree.test.ts) explicitly validates that index routes (e.g., '/a/', '/$a/', '/{-$a}/') consistently have trailing slashes, while non-index routes do not.

The check at lines 1744-1751 correctly prevents adding children to index routes using this reliable detection method.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/router-core/src/route.ts (1)

1678-1691: Use a stable identifier for error messages instead of this.id (initialized later).

At this point in init, _id hasn’t been assigned yet, so this.id is undefined and both invariant messages will print ...on 'undefined'..., which matches the earlier review note. You already have options in scope here; you can derive a stable identifier from options.id or options.path and reuse it for both messages.

Example:

-    if (process.env.NODE_ENV !== 'production') {
-      if (this.parentRoute) {
-        invariant(
-          this.parentRoute.isRoot || !this.parentRoute.fullPath.endsWith('/'),
-          `Parent route with id '${this.parentRoute.id}' returned by getParentRoute on '${this.id}' is an index route and cannot have child routes.`,
-        )
-      }
-      if (this.parentRoute) {
-        invariant(
-          this.parentRoute.children && this.parentRoute.children.includes(this),
-          `Parent route with id '${this.parentRoute.id}' returned by getParentRoute has no child route with id '${this.id}'. Did you forget to call .addChildren()?`,
-        )
-      }
-    }
+    if (process.env.NODE_ENV !== 'production') {
+      if (this.parentRoute) {
+        const routeIdForMessage =
+          options?.id ?? options?.path ?? this.id ?? '(unknown)'
+
+        invariant(
+          this.parentRoute.isRoot || !this.parentRoute.fullPath.endsWith('/'),
+          `Parent route with id '${this.parentRoute.id}' returned by getParentRoute on '${routeIdForMessage}' is an index route and cannot have child routes.`,
+        )
+
+        invariant(
+          this.parentRoute.children && this.parentRoute.children.includes(this),
+          `Parent route with id '${this.parentRoute.id}' returned by getParentRoute has no child route with id '${routeIdForMessage}'. Did you forget to call .addChildren()?`,
+        )
+      }
+    }

This keeps the checks as-is but guarantees a meaningful identifier in the messages even before _id is set.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 72feec7 and a7adfcb.

📒 Files selected for processing (1)
  • packages/router-core/src/route.ts (2 hunks)
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.
📚 Learning: 2025-10-08T08:11:47.088Z
Learnt from: nlynzaad
Repo: TanStack/router PR: 5402
File: packages/router-generator/tests/generator/no-formatted-route-tree/routeTree.nonnested.snapshot.ts:19-21
Timestamp: 2025-10-08T08:11:47.088Z
Learning: Test snapshot files in the router-generator tests directory (e.g., files matching the pattern `packages/router-generator/tests/generator/**/routeTree*.snapshot.ts` or `routeTree*.snapshot.js`) should not be modified or have issues flagged, as they are fixtures used to verify the generator's output and are intentionally preserved as-is.

Applied to files:

  • packages/router-core/src/route.ts
📚 Learning: 2025-10-14T18:59:33.990Z
Learnt from: FatahChan
Repo: TanStack/router PR: 5475
File: e2e/react-start/basic-prerendering/src/routes/redirect/$target/via-beforeLoad.tsx:8-0
Timestamp: 2025-10-14T18:59:33.990Z
Learning: In TanStack Router e2e test files, when a route parameter is validated at the route level (e.g., using zod in validateSearch or param validation), switch statements on that parameter do not require a default case, as the validation ensures only expected values will reach the switch.

Applied to files:

  • packages/router-core/src/route.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Preview
  • GitHub Check: Test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants